/**
 * 
 */
package qy.jalgotrade.technical;

import java.time.ZonedDateTime;

import qy.jalgotrade.utils.FastUtilDoubleArrayListDeque;
import qy.jalgotrade.utils.FastUtilIntArrayListDeque;
import qy.jalgotrade.utils.FastUtilLongArrayListDeque;
import qy.jalgotrade.utils.ListDeque;
import qy.jalgotrade.utils.PySpecs;

/**
 * <pre>
 * An EventWindow class is responsible for making calculation over a moving window of values.
 * pyalgotrade.technical.EventWindow: 使用移动窗口模式滚动计算指标; 需指定 windowSize; 实现类一般以 windowFull() 为 True 作为触发计算指标的 Event;
 * self.getValue(): 具体指标计算的实现方法 (通用逻辑: 使用 移动窗口 (一般要求窗口已满) 中的值, 进行相应的计算)
 * 
 * .. note::
 *     This is a base class and should not be used directly.
 * </pre>
 * 
 * <pre>
 * T: Type for SequenceDataSeries
 * U: input for EventWindow
 * V: output for EventWindow
 * </pre>
 * 
 * @author c-geo
 *
 */
public abstract class EventWindow<T> {

	/**
	 * TODO: to optimize
	 */
	private ListDeque<Object> __values;

	private boolean __elementOptimize;

	private Class<?> __elementType;

	private PySpecs.SeqGetItem<?> __primitiveValues;

	private int __windowSize;

	private boolean __skipNone;

	/**
	 * 
	 * @param windowSize The size of the window. Must be greater than 0.
	 */
	public EventWindow(int windowSize) {

		this(windowSize, true, Object.class);
	}

	/**
	 * 
	 * @param windowSize The size of the window. Must be greater than 0.
	 * @param elemType   For perf. optimization
	 */
	public EventWindow(int windowSize, Class<?> elemType) {

		this(windowSize, true, elemType);
	}

	/**
	 * 
	 * @param windowSize The size of the window. Must be greater than 0.
	 * @param skipNone   True if None values should not be included in the window.
	 */
	public EventWindow(int windowSize, boolean skipNone) {

		this(windowSize, skipNone, Object.class);
	}

	/**
	 * 
	 * @param windowSize The size of the window. Must be greater than 0.
	 * @param skipNone   True if None values should not be included in the window.
	 * @param elemType   For perf. optimization
	 */
	public EventWindow(int windowSize, boolean skipNone, Class<?> elemType) {

		assert windowSize > 0;

		__elementOptimize = false;
		__values = null;
		__primitiveValues = null;
		if (Integer.class.equals(elemType)) {

			__elementOptimize = true;
			__elementType = Integer.class;
			__primitiveValues = new FastUtilIntArrayListDeque(windowSize);
		} else if (Long.class.equals(elemType)) {

			__elementOptimize = true;
			__elementType = Long.class;
			__primitiveValues = new FastUtilLongArrayListDeque(windowSize);
		} else if (Double.class.equals(elemType)) {

			__elementOptimize = true;
			__elementType = Double.class;
			__primitiveValues = new FastUtilDoubleArrayListDeque(windowSize);
		} else {
			// Object.class.equals(elemType)
			__elementType = Object.class;
			__values = new ListDeque<>(windowSize);
		}

		__windowSize = windowSize;
		__skipNone = skipNone;
	}

	/**
	 * Returns the window size.
	 * 
	 * @return
	 */
	public int getWindowSize() {

		return __windowSize;
	}

	/**
	 * 
	 * @return
	 */
	public boolean windowFull() {

		if (__elementOptimize) {
			return __primitiveValues.__len__() == __windowSize;
		} else {
			return __values.__len__() == __windowSize;
		}
	}

	/**
	 * 
	 * @param dateTime
	 * @param value
	 */
	public void onNewValue(ZonedDateTime dateTime, Object value) {

		if (__elementOptimize) {
			if (Integer.class.equals(__elementType)) {

				if (value != null || !__skipNone) {
					((FastUtilIntArrayListDeque) __primitiveValues).append((int) value);
				}
			} else if (Long.class.equals(__elementType)) {

				if (value != null || !__skipNone) {
					((FastUtilLongArrayListDeque) __primitiveValues).append((long) value);
				}
			} else {
				// Double.class.equals(__elementType)
				if (value != null && !Double.isNaN((Double) value) || !__skipNone) {
					((FastUtilDoubleArrayListDeque) __primitiveValues).append((double) value);
				}
			}
		} else {
			if (value != null || !__skipNone) {
				__values.append(value);
			}
		}
	}

	/**
	 * Returns a numpy.array with the values in the window.
	 * 
	 * @return
	 */
	public Object getValues() {

		if (__elementOptimize) {
			if (Integer.class.equals(__elementType)) {

				return ((FastUtilIntArrayListDeque) __primitiveValues).data();

			} else if (Long.class.equals(__elementType)) {

				return ((FastUtilLongArrayListDeque) __primitiveValues).data();

			} else {
				// Double.class.equals(__elementType)
				return ((FastUtilDoubleArrayListDeque) __primitiveValues).data();
			}
		} else {
			return __values.data();
		}
	}

	/**
	 * 
	 * @return
	 */
	public Object toPrimitiveArray() {

		if (__elementOptimize) {
			if (Integer.class.equals(__elementType)) {

				return ((FastUtilIntArrayListDeque) __primitiveValues).toPrimitiveArray();

			} else if (Long.class.equals(__elementType)) {

				return ((FastUtilLongArrayListDeque) __primitiveValues).toPrimitiveArray();

			} else {
				// Double.class.equals(__elementType)
				return ((FastUtilDoubleArrayListDeque) __primitiveValues).toPrimitiveArray();
			}
		} else {
			return null;
		}
	}

	/**
	 * Override to calculate a value using the values in the window.
	 * 
	 * @return
	 */
	public abstract T getValue();
}
